package com.xlf.service.base.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.Assert;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageInfo;
import com.xlf.common.auth.util.SecurityUtils;
import com.xlf.common.core.constant.Constants;
import com.xlf.common.core.exception.BizException;
import com.xlf.common.core.pojo.vo.UserVO;
import com.xlf.common.core.util.BeanCopyUtils;
import com.xlf.common.core.util.StringUtils;
import com.xlf.common.core.util.page.PageDomain;
import com.xlf.common.core.util.page.PageUtils;
import com.xlf.common.core.util.page.TableDataInfo;
import com.xlf.common.core.util.page.TableSupport;
import com.xlf.common.redis.service.RedisCache;
import com.xlf.pojo.base.*;
import com.xlf.service.base.manager.ProblemCommonService;
import com.xlf.service.base.mapper.*;
import com.xlf.pojo.dto.ContestListReqDTO;
import com.xlf.pojo.dto.ContestRecordDTO;
import com.xlf.service.base.pojo.dto.RegisterContestDTO;
import com.xlf.service.base.pojo.vo.*;
import com.xlf.service.base.service.ContestService;
import com.xlf.service.base.service.impl.validate.ContestValidator;
import com.xlf.service.system.service.impl.CommonUserVOService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;
import java.util.function.BiPredicate;
import java.util.stream.Collectors;

import static com.xlf.common.core.constant.RedisConst.RANKS;
import static com.xlf.common.redis.service.RedisCache.LOCK_KEY;
import static com.xlf.pojo.constant.Constants.successJudgeList;

/**
* @author 小新
* @description 针对表【contest】的数据库操作Service实现
* @createDate 2023-12-15 11:24:01
*/
@Service
public class ContestServiceImpl extends ServiceImpl<ContestMapper, Contest>
    implements ContestService{


    @Resource
    CommonUserVOService commonUserVOService;

    @Resource
    ContestRegisterMapper contestRegisterMapper;

    @Resource
    ContestProblemMapper contestProblemMapper;

    @Resource
    ProblemMapper problemMapper;

    @Resource
    JudgeMapper judgeMapper;

    @Autowired
    ContestValidator contestValidator;

    @Resource
    RedisCache redisCache;

    @Autowired
    ProblemCommonService problemCommonService;

    @Autowired
    public RedissonClient redissonClient;



    @Override
    public List<Contest> getList(Contest contest) {

        LambdaQueryChainWrapper<Contest> lambdaQuery = lambdaQuery().select(Contest::getId, Contest::getTitle,
                Contest::getStartTime, Contest::getEndTime,Contest::getProgress,
                Contest::getDuration, Contest::getType, Contest::getAuth,
                Contest::getUid ,Contest::getStatus);

        Integer progress = contest.getProgress();
        queryProgress(lambdaQuery, progress);

        List<Contest> contestList = lambdaQuery
                .eq(StringUtils.isNotNull(contest.getId()), Contest::getId, contest.getId())
                .eq(StringUtils.isNotNull(contest.getType()), Contest::getType, contest.getType())
                .eq(StringUtils.isNotNull(contest.getAuth()), Contest::getAuth, contest.getAuth())
                .like(StringUtils.isNotEmpty(contest.getTitle()), Contest::getTitle, contest.getTitle())
                .in(StringUtils.isNotNull(contest.getUid()), Contest::getUid, contest.getUid())
                .orderByDesc(Contest::getCreateTime).list();

        if(StringUtils.isNotNull(contestList)){
            Map<Long, UserVO> userInfoMap = commonUserVOService
                    .getUserInfoMap(contestList.stream()
                            .filter(item -> item.getUid()!=null)
                            .map(item -> item.getUid().toString())
                            .collect(Collectors.toSet()));
            contestList.forEach(item -> {

                item.setProblemNum(Math.toIntExact(contestProblemMapper
                        .selectCount(new LambdaQueryWrapper<ContestProblem>()
                                .eq(ContestProblem::getCid, item.getId()))));
                item.setRegistersNum(Math.toIntExact(contestRegisterMapper
                        .selectCount(new LambdaQueryWrapper<ContestRegister>()
                                .eq(ContestRegister::getCid, item.getId()))));

                item.setAuthor(BeanUtil.copyProperties(userInfoMap.get(item.getUid()), com.xlf.pojo.vo.UserVO.class));
            });
        }

        return contestList;
    }

    @Override
    public boolean saveOrUpdate_(Contest contest) {

        Object userId = SecurityUtils.getUserId();
        contest.setUid((Long) userId);

        return saveOrUpdate(contest);
    }

    @Override
    public Contest getConTest(Long id) {

        Contest contest = getById(id);
        Assert.notNull(contest,() -> new BizException("比赛不存在！"));

        return contest;
    }

    @Transactional
    @Override
    public void delete(List<Long> ids) {

    }

    @Override
    public boolean updateStatus(Contest contest) {
        return lambdaUpdate().set(Contest::getStatus,contest.getStatus()).eq(Contest::getId,contest.getId()).update();
    }

    @Override
    public TableDataInfo<ContestListVO> getContestListVO(ContestListReqDTO contestListVO) {

        Set<Long> userIds = new HashSet<>();

        if(StringUtils.isNotEmpty(contestListVO.getNickName())){
            userIds = commonUserVOService.getUserIdsByNickName(contestListVO.getNickName());
            if(StringUtils.isEmpty(userIds)){
                return PageUtils.getEmptyDataTable();
            }
        }

        LambdaQueryChainWrapper<Contest> lambdaQuery = lambdaQuery()
                .select(Contest::getId, Contest::getTitle,
                Contest::getStartTime, Contest::getEndTime,
                Contest::getDuration, Contest::getProgress,
                Contest::getType, Contest::getAuth,
                Contest::getUid);

        lambdaQuery.eq(Contest::getStatus, Constants.NORMAL);

        queryProgress(lambdaQuery, contestListVO.getProgress());

        List<Contest> contestList = lambdaQuery
                .eq(StringUtils.isNotNull(contestListVO.getContestId()), Contest::getId, contestListVO.getContestId())
                .eq(StringUtils.isNotNull(contestListVO.getType()), Contest::getType, contestListVO.getType())
                .eq(StringUtils.isNotNull(contestListVO.getAuth()), Contest::getAuth, contestListVO.getAuth())
                .like(StringUtils.isNotEmpty(contestListVO.getTitle()), Contest::getTitle, contestListVO.getTitle())
                .in(StringUtils.isNotEmpty(userIds), Contest::getUid, userIds)
                .orderByDesc(Contest::getCreateTime).list();


        if(StringUtils.isEmpty(contestList))
            return PageUtils.getEmptyDataTable();

        List<ContestListVO> contestListVOS = BeanUtil.copyToList(contestList, ContestListVO.class);

        Map<Long, UserVO> userInfoMap = commonUserVOService.getUserInfoMap(contestListVOS.stream()
                .map(item -> item.getUid().toString())
                .collect(Collectors.toSet()));
        contestListVOS.forEach(item -> {
            item.setUserVO(userInfoMap.get(item.getUid()));
        });
        return PageUtils.getDataTable(contestListVOS,new PageInfo(contestList).getTotal());
    }

    @Override
    public ProblemDetailVO getContestProblemDetailVO(Long problemId) {

        Problem problem = problemCommonService.selectProblem(problemId);

        ProblemDetailVO problemDetailVO = problemCommonService.toProblemDetailVO(problem);

        problemDetailVO.setLanguageVOS(problemCommonService.getLanguageVOHashMap(problemId));

        return problemDetailVO;

    }

    /**
     * 比赛进程限制
     * @param lambdaQuery
     * @param progress
     */
    private void queryProgress(LambdaQueryChainWrapper<Contest> lambdaQuery, Integer progress) {

        //0为未开始,1为进行中，2为已结束 3，包括未开始和进行中
        if(StringUtils.isNotNull(progress)){
            Date date = new Date();
            if(progress.equals(0)){
                lambdaQuery.gt(Contest::getStartTime,date);
            }else if(progress.equals(1)){
                lambdaQuery.le(Contest::getStartTime,date);
                lambdaQuery.ge(Contest::getEndTime,date);
            }else if(progress.equals(2)){
                lambdaQuery.lt(Contest::getEndTime,date);
            }
        }
    }


    @Override
    public ContestVO getContestVO(Long id) {

        Contest contest = lambdaQuery().select(Contest::getId, Contest::getTitle,
                        Contest::getDescription, Contest::getStartTime, Contest::getEndTime,
                        Contest::getDuration, Contest::getProgress,
                        Contest::getPwd, Contest::getType, Contest::getAuth,
                        Contest::getUid, Contest::getDescription)
                .eq(Contest::getId, id).one();

        Assert.notNull(contest,() -> new BizException("比赛不存在"));

        ContestVO contestVO = BeanUtil.copyProperties(contest, ContestVO.class);

        contestVO.setUserVO(commonUserVOService.getUserInfo(contestVO.getUid()));

        //是否报名
        if(contestValidator.contestRegisterValidator(contest.getId()))
            contestVO.setRegister(1);
        else
            contestVO.setRegister(0);

        //是否需要密码
        if(StringUtils.isNotEmpty(contest.getPwd()))
            contestVO.setCheck(1);
        else
            contestVO.setCheck(0);

        //报名人数
        contestVO.setRegisterNum(contestRegisterMapper.selectCount(new LambdaQueryWrapper<ContestRegister>()
                .eq(ContestRegister::getCid, contestVO.getId())).intValue());

        return contestVO;
    }

    @Override
    public List<ContestProblem> getProblemList(Long cid) {

        List<ContestProblem> contestProblems = contestProblemMapper.selectList(new LambdaQueryWrapper<ContestProblem>()

                .eq(ContestProblem::getCid, cid));

        if(StringUtils.isNotEmpty(contestProblems)){
            contestProblems.forEach(item -> {
                item.setProblem(problemMapper.selectOne(new LambdaQueryWrapper<Problem>()
                                .select(Problem::getId,Problem::getProblemId,Problem::getTitle,Problem::getAuth,Problem::getCreateTime)
                        .eq(Problem::getId,item.getPid())));
            });
        }else{
            return new ArrayList<>();
        }
        return contestProblems;
    }

    @Override
    public List<ContestProblemVO> getContestProblemVO(Long cid) {

        Contest contest = contestValidator.queryValidator(cid);

        List<ContestProblem> contestProblems = contestProblemMapper
                .selectList(new LambdaQueryWrapper<ContestProblem>()
                        .eq(ContestProblem::getCid, cid)
                        .eq(ContestProblem::getStatus,Constants.NORMAL));

        if(StringUtils.isEmpty(contestProblems))
            return new ArrayList<>();

        List<ContestProblemVO> contestProblemVOS = BeanUtil.copyToList(contestProblems, ContestProblemVO.class);


        Set<Long> problemIds = contestProblems.stream().map(ContestProblem::getPid).collect(Collectors.toSet());

        Object userId = SecurityUtils.getUserId();

        List<Judge> judges = Optional.of(judgeMapper.selectList(new LambdaQueryWrapper<Judge>()
                .in(Judge::getPid, problemIds)
                .eq(Judge::getStatus,successJudgeList)
                .eq(Judge::getUserId, userId)
                .eq(Judge::getCid, cid))).orElse(new ArrayList<>());

        // 0 未开始 1 尝试过 2 AC
        HashMap<Long, Integer> judgeStatus = new HashMap<>();

        for (Judge judge : judges) {
            Long key = judge.getPid();
            if(judge.getStatus().equals(com.xlf.pojo.constant.Constants.Judge.STATUS_ACCEPTED.getStatus())){
                judgeStatus.put(key,2);
            } else {
                if(!judgeStatus.containsKey(key))
                    judgeStatus.put(key,1);
            }

        }


        contestProblemVOS.forEach(item -> {

            Problem problem = Optional.of(problemMapper.selectOne(new LambdaQueryWrapper<Problem>()
                    .select(Problem::getId ,Problem::getTitle,Problem::getIoScore)
                    .eq(Problem::getId, item.getPid())
                    .eq(Problem::getStatus,Constants.NORMAL)))
                    .orElseGet(() -> new Problem());

            item.setTitle(problem.getTitle());

            if(contest.getType().equals(com.xlf.pojo.constant.Constants.Contest.TYPE_OI.getCode()))
                item.setIoScore(problem.getIoScore());

            if(judgeStatus.containsKey(problem.getId()))
                item.setStatus(judgeStatus.get(problem.getId()));
            else
                item.setStatus(0);

            Long acNum = judgeMapper.selectCount(new LambdaQueryWrapper<Judge>()
                    .eq(Judge::getCid, cid)
                    .eq(Judge::getPid,item.getPid())
                    .eq(Judge::getStatus, com.xlf.pojo.constant.Constants.Judge.STATUS_ACCEPTED));
            Long submitNum = judgeMapper.selectCount(new LambdaQueryWrapper<Judge>()
                    .eq(Judge::getCid, cid)
                    .eq(Judge::getPid,item.getPid()));
            item.setAcSum(acNum.intValue());
            item.setSubmitSum(submitNum.intValue());

        });

        return contestProblemVOS;

    }

    /**
     * 获取排行榜比赛题目参照
     * @param cid
     * @return
     */
    @Override
    public List<ContestProblemReVO> getContestProblemReVO(Long cid) {

        contestValidator.queryValidator(cid);

        List<ContestProblem> contestProblems = contestProblemMapper
                .selectList(new LambdaQueryWrapper<ContestProblem>()
                        .select(ContestProblem::getPid,ContestProblem::getSeq)
                        .eq(ContestProblem::getCid, cid)
                        .eq(ContestProblem::getStatus,Constants.NORMAL));


        if(StringUtils.isEmpty(contestProblems))
            return new ArrayList<>();

        List<ContestProblemReVO> contestProblemVOS = BeanUtil.copyToList(contestProblems, ContestProblemReVO.class);


        return contestProblemVOS;
    }


    @Override
    public List<ContestRegister> getRegisterUserList(Long cid) {

        List<ContestRegister> contestRegister = contestRegisterMapper
                .selectList(new LambdaQueryWrapper<ContestRegister>().eq(ContestRegister::getCid, cid));

        if(StringUtils.isNotEmpty(contestRegister)){

            Map<Long, UserVO> userInfoMap = commonUserVOService.getUserInfoMap(contestRegister.stream()
                    .filter(item -> item.getUid()!=null)
                    .map(item -> item.getUid().toString())
                    .collect(Collectors.toSet()));

            contestRegister.forEach(item -> {
                item.setUserVO(BeanCopyUtils.copyObject(userInfoMap.get(item.getUid()), com.xlf.pojo.vo.UserVO.class));
            });
        }else{
            return new ArrayList<>();
        }
        return contestRegister;
    }


    @Transactional
    @Override
    public void addContestProblem(List<ContestProblem> contestProblems) {

        if(StringUtils.isEmpty(contestProblems))
            throw new BizException("题目列表为空！");

        contestProblems.forEach(item -> {
            if(StringUtils.isNull(item.getCid()) || StringUtils.isNull(item.getPid()))
                throw new BizException("数据格式错误！");
        });

        List<ContestProblem> contestProblemList = contestProblemMapper.selectList(new LambdaQueryWrapper<ContestProblem>()
                .select(ContestProblem::getPid,ContestProblem::getId)
                .eq(ContestProblem::getCid, contestProblems.get(0).getCid()));
        if(StringUtils.isEmpty(contestProblemList))
            contestProblemList = new ArrayList<>();

        //新增 a-b
        getDifferenceSet(contestProblems, contestProblemList, (a, b) -> a.getPid().equals(b.getPid()))
                .forEach(item -> contestProblemMapper.insert(item));

        //删除 b-a
        getDifferenceSet(contestProblemList,contestProblems, (a, b) -> a.getPid().equals(b.getPid()))
                .forEach(item -> contestProblemMapper.deleteById(item.getId()));

    }

    @Transactional
    @Override
    public void addRegisterUser(List<ContestRegister> contestRegisters) {

        if(StringUtils.isEmpty(contestRegisters))
            throw new BizException("用户列表为空！");

        contestRegisters.forEach(item -> {
            if(StringUtils.isNull(item.getCid()) || StringUtils.isNull(item.getUid()))
                throw new BizException("数据格式错误！");
        });

        List<ContestRegister> contestRegisterList = contestRegisterMapper.selectList(new LambdaQueryWrapper<ContestRegister>()
                .select(ContestRegister::getUid,ContestRegister::getId)
                .eq(ContestRegister::getCid, contestRegisters.get(0).getCid()));
        if(StringUtils.isEmpty(contestRegisterList))
            contestRegisterList = new ArrayList<>();

        //新增 a-b
        getDifferenceSet(contestRegisters, contestRegisterList, (a, b) -> a.getUid().equals(b.getUid()))
                .forEach(item -> {
                    contestRegisterMapper.insert(item);
                    String zKey = RANKS + item.getCid().toString();
                    redisCache.setCacheZSet(zKey,item.getUid().toString(),0);

                });

        //删除 b-a
        getDifferenceSet(contestRegisterList,contestRegisters, (a, b) -> a.getUid().equals(b.getUid()))
                .forEach(item -> {
                    contestRegisterMapper.deleteById(item.getId());
                    String zKey = RANKS + item.getCid().toString();
                    redisCache.removeZSet(zKey,item.getUid().toString());
                });

    }


    @Override
    public TableDataInfo getContestByRedisRank(Long cid) {

        Contest contest = contestValidator.queryValidator(cid);

        PageDomain pageDomain = TableSupport.buildPageRequest();
        Integer pageNum = pageDomain.getPageNum();
        Integer pageSize = pageDomain.getPageSize();

        String key = RANKS + cid.toString();

        //分页获取排行榜信息
        Set<String> userIds = redisCache.getCacheZSet(key, pageNum, pageSize);

        TableDataInfo pageResult = new TableDataInfo();
        ArrayList<Object> res = new ArrayList<>();
        pageResult.setRows(res);
        long total = redisCache.getZSetSize(key);
        pageResult.setTotal(total);

        //空
        if(StringUtils.isEmpty(userIds)){
            return PageUtils.getEmptyDataTable();
        }

        //ACM
        if ((com.xlf.pojo.constant.Constants.Contest.TYPE_ACM.getCode()).equals(contest.getType())) {
            userIds.forEach(item -> {

            });
         //OI
        } else if((com.xlf.pojo.constant.Constants.Contest.TYPE_OI.getCode()).equals(contest.getType())){

            //获取用户信息
            Map<Long, UserVO> userInfoMap = commonUserVOService.getUserInfoMap(userIds);

            //本人得分
            Long meId = (Long)SecurityUtils.getUserId();
            //获取得分
            OIContestRankVO oiContestRankVO = getOiContestRankVO(key, meId.toString());
            //获取排行，如果未注册，排行为0
            Long rank = Optional.of(redisCache.getCacheZSetByKey(key, meId.toString()))
                    .orElse(0L);
            oiContestRankVO.setRank(rank.intValue() + 1);
            //获取用户信息
            oiContestRankVO.setUserInfo(commonUserVOService.getUserInfo(meId));

            res.add(oiContestRankVO);

            Integer ranks = 1;
            for (String userId : userIds) {
                //获取得分
                oiContestRankVO = getOiContestRankVO(key, userId);
                oiContestRankVO.setRank(ranks);
                oiContestRankVO.setUserInfo(userInfoMap.get(Long.parseLong(userId)));
                ranks++;
                res.add(oiContestRankVO);
            }
        }else{
            throw new BizException("比赛类型错误！");
        }

        return pageResult;
    }

    @Override
    public TableDataInfo getContestByMysqlRank(Long cid) {

        Contest contest = contestValidator.queryValidator(cid);

        //获取报名的用户
        List<ContestRegister> contestRegisterList = contestRegisterMapper.selectList(new LambdaQueryWrapper<ContestRegister>()
                .eq(ContestRegister::getCid, cid)
                .eq(ContestRegister::getStatus, Constants.NORMAL));


        if(StringUtils.isEmpty(contestRegisterList))
            return PageUtils.getEmptyDataTable();

        TableDataInfo tableDataInfo = new TableDataInfo();

        Object userId = SecurityUtils.getUserId();

        //ACM  每个用户取通过的题目数量的总和进行排序（通过题数相同的情况下按照答题时间+罚时来排名）
        if ((com.xlf.pojo.constant.Constants.Contest.TYPE_ACM.getCode()).equals(contest.getType())) {

            int total = contestRegisterList.size();
            //最终结果
            List<ACMContestRankVO> acmContestRankVOS = new ArrayList<ACMContestRankVO>(total);
            //本次比赛的所有判题信息
            List<Judge> judges = Optional.of(judgeMapper.selectList(new LambdaQueryWrapper<Judge>()
                    .eq(Judge::getCid,cid)
                    .in(Judge::getStatus, successJudgeList))).orElse(new ArrayList<>());

            //分页参数合理化
            PageDomain pageDomain = TableSupport.buildPageRequest();
            Integer pageNum = pageDomain.getPageNum();
            Integer pageSize = pageDomain.getPageSize();
            int startIndex = (pageNum - 1) * pageSize;
            if(startIndex >= total)
                return PageUtils.getEmptyDataTable();
            if (startIndex < 0) {
                startIndex = 0;
            }
            int endIndex = pageNum * pageSize - 1;
            if (endIndex >= total) {
                endIndex = total - 1;
            }

            long contestTime = contest.getStartTime().getTime();

            ACMContestRankVO meAcmContestRankVO = null;

            //遍历用户，封装判题信息
            for (ContestRegister contestRegister : contestRegisterList) {

                ACMContestRankVO acmContestRankVO = new ACMContestRankVO();

                HashMap<Long, ACMContestRankVO.ContestRecordVO> hashMap = new HashMap<>();


                for (Judge judge : judges) {

                    if(!judge.getUserId().equals(contestRegister.getUid()))
                        continue;


                    Long pid = judge.getPid();
                    ACMContestRankVO.ContestRecordVO contestRecordVO = hashMap.get(pid);

                    if(contestRecordVO == null){
                        contestRecordVO = new ACMContestRankVO.ContestRecordVO();
                        contestRecordVO.setAc(0);
                        contestRecordVO.setFailTotal(0);
                    }
                    //AC
                    if(judge.getStatus().equals(com.xlf.pojo.constant.Constants.Judge.STATUS_ACCEPTED.getStatus())){
                        contestRecordVO.setAc(1);
                        long time = judge.getSubmitTime().getTime();
                        if(contestRecordVO.getAcTime() == null)
                            contestRecordVO.setAcTime(time - contestTime);
                        else
                            contestRecordVO.setAcTime(Math.min(time - contestTime , contestRecordVO.getAcTime()));

                    }else {
                        //未AC
                        contestRecordVO.setFailTotal(contestRecordVO.getFailTotal() + 1);

                    }
                    hashMap.put(pid,contestRecordVO);
                }

                int acTotal = 0;
                int failTotal = 0;
                long maxSubmitDate = 0;
                for (Map.Entry<Long, ACMContestRankVO.ContestRecordVO> longContestRecordVOEntry : hashMap.entrySet()) {
                    ACMContestRankVO.ContestRecordVO value = longContestRecordVOEntry.getValue();
                    if(value.getAc().equals(1)){
                        ++acTotal;
                        failTotal += value.getFailTotal();
                        maxSubmitDate = Math.max(maxSubmitDate , value.getAcTime());
                    }
                }

                acmContestRankVO.setUserId(contestRegister.getUid());
                acmContestRankVO.setSubmissionInfo(hashMap);
                acmContestRankVO.setAcTotal(acTotal);
                acmContestRankVO.setTotalTime(maxSubmitDate +  failTotal * 20 * 60 * 1000);

                acmContestRankVOS.add(acmContestRankVO);

                if(contestRegister.getUid().equals((Long) userId)){
                    meAcmContestRankVO = acmContestRankVO;
                }
            }

            //排序截取分页
            acmContestRankVOS.sort((o1, o2) -> {
                if(o1.getAcTotal() > o2.getAcTotal())
                    return -1;
                else if(o1.getAcTotal().equals(o2.getAcTotal())){
                    if(o1.getTotalTime() < o2.getTotalTime())
                        return -1;
                }
                return 1;
            });
            List<ACMContestRankVO> res = acmContestRankVOS.subList(startIndex, endIndex + 1);


            //计算排行
            int offset = 1;
            for (ACMContestRankVO acmContestRankVO : res) {
                acmContestRankVO.setRank(startIndex + offset);
                offset++;
            }

//            if(meAcmContestRankVO != null)
//                res.add(0,meAcmContestRankVO);
            //获取用户信息
            Set<String> userIds = res.stream().map(item -> item.getUserId().toString()).collect(Collectors.toSet());
            Map<Long, UserVO> userInfoMap = commonUserVOService.getUserInfoMap(userIds);
            res.forEach(item -> item.setUserInfo(userInfoMap.get(item.getUserId())));

            tableDataInfo.setRows(res);
            tableDataInfo.setTotal(total);


            //OI 每个用户取每道题多次提交的最高分（同分取运行时间小的）的总和进行排序
        } else if((com.xlf.pojo.constant.Constants.Contest.TYPE_OI.getCode()).equals(contest.getType())) {

            int total = contestRegisterList.size();
            //最终结果
            ArrayList<OIContestRankVO> oiContestRankVOS = new ArrayList<>(total);

            //本次比赛的所有判题信息
            List<Judge> judges = Optional.of(judgeMapper.selectList(new LambdaQueryWrapper<Judge>()
                            .eq(Judge::getCid,cid)
                            .in(Judge::getStatus,successJudgeList))).orElse(new ArrayList<>());

            //分页参数合理化
            PageDomain pageDomain = TableSupport.buildPageRequest();
            Integer pageNum = pageDomain.getPageNum();
            Integer pageSize = pageDomain.getPageSize();
            int startIndex = (pageNum - 1) * pageSize;
            if(startIndex >= total)
                return PageUtils.getEmptyDataTable();
            if (startIndex < 0) {
                startIndex = 0;
            }
            int endIndex = pageNum * pageSize - 1;
            if (endIndex >= total) {
                endIndex = total - 1;
            }

            OIContestRankVO meOiContestRankVO = null;

            //遍历用户，封装判题信息
            for (ContestRegister contestRegister : contestRegisterList) {

                OIContestRankVO oiContestRankVO = new OIContestRankVO();
                 Integer totalScore = 0;
                 Integer totalTime = 0;
                HashMap<String, ContestRecordDTO> hashMap = new HashMap<>();

                for (Judge judge : judges) {
                    if(!judge.getUserId().equals(contestRegister.getUid()))
                        continue;
                    String pid = judge.getPid().toString();
                    if(hashMap.containsKey(pid)){
                        ContestRecordDTO oldContestRecordDTO = hashMap.get(pid);
                        if(judge.getScore() > oldContestRecordDTO.getScore()){
                            //新得分
                            ContestRecordDTO contestRecordDTO = getContestRecordDTO(judge);
                            hashMap.put(pid,contestRecordDTO);
                            totalScore += (judge.getScore() - oldContestRecordDTO.getScore());

                            //更多分
                        }else if(judge.getScore().equals(oldContestRecordDTO.getScore())){

                            //同样的分数花的时间更少
                            if(judge.getTime() < oldContestRecordDTO.getTime()){
                                ContestRecordDTO contestRecordDTO = getContestRecordDTO(judge);
                                hashMap.put(pid,contestRecordDTO);
                                totalTime += (judge.getTime() - oldContestRecordDTO.getTime());

                            }
                        }
                    }else {
                        ContestRecordDTO contestRecordDTO = getContestRecordDTO(judge);
                        hashMap.put(pid,contestRecordDTO);
                        totalTime += contestRecordDTO.getTime();
                        totalScore += contestRecordDTO.getScore();

                    }
                }

                oiContestRankVO.setUserId(contestRegister.getUid());
                oiContestRankVO.setTotalTime(totalTime);
                oiContestRankVO.setTotalScore(totalScore);
                oiContestRankVO.setSubmissionInfo(hashMap);
                oiContestRankVOS.add(oiContestRankVO);

                if(contestRegister.getUid().equals((Long) userId))
                    meOiContestRankVO = oiContestRankVO;
            }

            //排序截取分页
            oiContestRankVOS.sort((o1, o2) -> {
                    if(o1.getTotalScore() > o2.getTotalScore())
                        return -1;
                    else if(o1.getTotalScore().equals(o2.getTotalScore())){
                        if(o1.getTotalTime() < o2.getTotalTime())
                            return -1;
                    }
                    return 1;
                });
            List<OIContestRankVO> res = oiContestRankVOS.subList(startIndex, endIndex + 1);

            //计算排行
            int offset = 1;
            for (OIContestRankVO contestRankVO : res) {
                contestRankVO.setRank(startIndex + offset);
                offset++;
            }

//            if(meOiContestRankVO != null){
//                res.add(0,meOiContestRankVO);
//            }
            //获取用户信息
            Set<String> userIds = res.stream().map(item -> item.getUserId().toString()).collect(Collectors.toSet());
            Map<Long, UserVO> userInfoMap = commonUserVOService.getUserInfoMap(userIds);
            res.forEach(item -> item.setUserInfo(userInfoMap.get(item.getUserId())));

            tableDataInfo.setRows(res);
            tableDataInfo.setTotal(total);

        }
        return tableDataInfo;
    }

    private ContestRecordDTO getContestRecordDTO(Judge judge) {
        ContestRecordDTO contestRecordDTO = new ContestRecordDTO();
        contestRecordDTO.setJudgeId(judge.getId());
        contestRecordDTO.setMemory(judge.getMemory());
        contestRecordDTO.setTime(judge.getTime());
        contestRecordDTO.setScore(judge.getScore());
        contestRecordDTO.setOiRankScore(judge.getOiRankScore());
        return contestRecordDTO;
    }

    /**
     * 从缓存的比赛排行榜中获取userId的每道题得分情况
     * @param key 比赛缓存的key
     * @param userId 该用户id
     * @return 用户得分VO
     */
    private OIContestRankVO getOiContestRankVO(String key, String userId) {

        OIContestRankVO oiContestRankVO = new OIContestRankVO();
        Map<String, ContestRecordDTO> cacheMap = redisCache.getCacheMap(key + ':' + userId);
        if(StringUtils.isEmpty(cacheMap)){
            //没有提交纪录
            cacheMap = new HashMap<>();
        }
        //总得分
        int totalScore = cacheMap.values().stream().mapToInt(ContestRecordDTO::getScore)
                .reduce(0, Integer::sum);
        oiContestRankVO.setTotalScore(totalScore);
        //每道题的得分情况
        oiContestRankVO.setSubmissionInfo(cacheMap);
        return oiContestRankVO;
    }

    @Override
    public TableDataInfo<RecentlyContestVO> getRecentlyContest() {

        LambdaQueryChainWrapper<Contest> lambdaQuery = lambdaQuery().select(Contest::getId, Contest::getTitle,
                Contest::getStartTime,Contest::getType,Contest::getAuth);

        lambdaQuery.eq(Contest::getStatus, Constants.NORMAL);

        //未开始和进行中
        lambdaQuery.gt(Contest::getEndTime,new Date());

        PageUtils.startPage();
        List<Contest> list = lambdaQuery.orderByAsc(Contest::getStartTime).list();

        if(StringUtils.isEmpty(list))
            return PageUtils.getEmptyDataTable();

        List<RecentlyContestVO> recentlyContestVOS = BeanUtil.copyToList(list, RecentlyContestVO.class);

        return PageUtils.getDataTable(recentlyContestVOS,new PageInfo(list).getTotal());

    }

    @Override
    public ContestInfoVO getContestInfo(Long cid) {

        Contest contest = contestValidator.queryValidator(cid);

        List<ContestProblem> contestProblems = contestProblemMapper
                .selectList(new LambdaQueryWrapper<ContestProblem>()
                        .eq(ContestProblem::getCid, cid)
                        .eq(ContestProblem::getStatus,Constants.NORMAL));

        ContestInfoVO contestInfoVO = new ContestInfoVO();
        contestInfoVO.setTitle(contest.getTitle());
        contestInfoVO.setStartTime(contest.getStartTime());
        contestInfoVO.setEndTime(contest.getEndTime());
        contestInfoVO.setId(contest.getId());
        contestInfoVO.setType(contest.getType());

        if(StringUtils.isEmpty(contestProblems)){
            contestInfoVO.setProblems(new ArrayList<>());
            return contestInfoVO;
        }

        List<ContestInfoVO.ProblemVO> contestProblemVOS = BeanUtil.copyToList(contestProblems, ContestInfoVO.ProblemVO.class);

        Set<Long> problemIds = contestProblems.stream().map(ContestProblem::getPid).collect(Collectors.toSet());

        Object userId = SecurityUtils.getUserId();
        List<Judge> judges = Optional.of(judgeMapper.selectList(new LambdaQueryWrapper<Judge>()
                .select(Judge::getStatus,Judge::getPid)
                .in(Judge::getPid, problemIds)
                .in(Judge::getStatus,successJudgeList)
                .eq(Judge::getUserId, userId)
                .eq(Judge::getCid, cid))).orElse(new ArrayList<>());

        // 0 未开始 1 尝试过 2 AC
        HashMap<Long, Integer> judgeStatus = new HashMap<>();

        for (Judge judge : judges) {
            Long key = judge.getPid();
            if(judge.getStatus().equals(com.xlf.pojo.constant.Constants.Judge.STATUS_ACCEPTED.getStatus())){
                judgeStatus.put(key,2);
            } else {
                if(!judgeStatus.containsKey(key))
                    judgeStatus.put(key,1);
            }
        }

        contestProblemVOS.forEach(item -> {
            Problem problem = Optional.of(problemMapper.selectOne(new LambdaQueryWrapper<Problem>()
                            .select(Problem::getId ,Problem::getTitle)
                            .eq(Problem::getId, item.getPid())
                            .eq(Problem::getStatus,Constants.NORMAL)))
                    .orElseGet(() -> new Problem());
            item.setTitle(problem.getTitle());
            if(judgeStatus.containsKey(problem.getId()))
                item.setSubmitStatus(judgeStatus.get(problem.getId()));
            else
                item.setSubmitStatus(0);
        });

        contestInfoVO.setProblems(contestProblemVOS);
        return contestInfoVO;
    }

    @Transactional
    @Override
    public void updateContestProblem(ContestProblem contestProblems) {

    if(StringUtils.isNull(contestProblems) ||
            StringUtils.isNull(contestProblems.getId()))
        throw new BizException("数据格式错误！");

        contestProblemMapper.updateById(contestProblems);
    }


    @Override
    public void register(RegisterContestDTO registerContestDTO) {

        Contest contest = getOne(new LambdaQueryWrapper<Contest>()
                .select(Contest::getPwd,Contest::getId,Contest::getStartTime,Contest::getEndTime,Contest::getAuth)
                .eq(Contest::getId,registerContestDTO.getCid())
                .eq(Contest::getStatus,Constants.NORMAL));

        Assert.notNull(contest,() -> new BizException("比赛不存在"));

        if (StringUtils.isNotEmpty(contest.getPwd()) &&
                !contest.getPwd().equals(registerContestDTO.getPassword())) {
            throw new BizException("比赛密码错误，请重新输入！");
        }

        Date date  = new Date();

        if(date.after(contest.getEndTime()))
            throw new BizException("比赛已结束！");

        Long userId = (Long)SecurityUtils.getUserId();

        ContestRegister contestRegister = contestRegisterMapper.selectOne(new LambdaQueryWrapper<ContestRegister>()
                .eq(ContestRegister::getUid, userId)
                .eq(ContestRegister::getCid, contest.getId()));
        if(StringUtils.isNotNull(contestRegister) && contestRegister.getStatus().equals(Constants.NORMAL))
            throw new BizException("已报名成功！");

        if(StringUtils.isNotNull(contestRegister) && contestRegister.getStatus().equals(Constants.EXCEPTION))
            throw new BizException("已被禁用，联系管理员处理！");


        String lockKey = LOCK_KEY  + userId + ":register:" + contest.getId();
        RLock lock = redissonClient.getLock(lockKey);

        lock.lock();// 加锁自旋

        try {

             contestRegister = contestRegisterMapper.selectOne(new LambdaQueryWrapper<ContestRegister>()
                    .eq(ContestRegister::getUid, userId)
                    .eq(ContestRegister::getCid, contest.getId()));
            if(StringUtils.isNotNull(contestRegister) && contestRegister.getStatus().equals(Constants.NORMAL))
                throw new BizException("已报名成功！");

            if(StringUtils.isNotNull(contestRegister) && contestRegister.getStatus().equals(Constants.EXCEPTION))
                throw new BizException("已被禁用，联系管理员处理！");

            contestRegisterMapper.insert(ContestRegister.builder()
                    .cid(registerContestDTO.getCid())
                    .uid(userId)
                    .status(Constants.NORMAL)
                    .build());
            String zKey = RANKS + contest.getId().toString();

            redisCache.setCacheZSet(zKey,userId.toString(),0);

        }finally {
            // 7.释放锁
            lock.unlock();
        }

    }


    /**
     * 求 a-b的差集
     * @param a
     * @param b
     * @param equals 判断 a，b相等的依据
     * @param <T>
     * @return
     */
    private  <T> List<T> getDifferenceSet(List<T> a, List<T> b, BiPredicate<T,T> equals) {

        List<T> tList = new ArrayList<>();
        for (int i = 0; i < a.size(); i++) {

            boolean isOk = true;
            for (int j = 0; j < b.size(); j++) {
                if(equals.test(a.get(i),b.get(j))){
                    isOk = false;
                    break;
                }
            }
            if(isOk)
                tList.add(a.get(i));
        }
        return tList;
    }

}




